class Complier {
  constructor(vm) {
    this.el = vm.$el
    this.vm = vm
    this.complier(this.el)
  }
  // 编译模板，处理文本节点和元素节点
  complier(el) {
    // 我们先获取 el 下的所有节点
    let childNodes = el.childNodes
    // 我们需要遍历 el 下的所有节点
    Array.from(childNodes).forEach(node => {
      // 接下来我们需要判断节点的类型
      if (this.isTextNode(node)) {
        // 如果是文本节点我们需要调用 文本节点的编译方法，处理差值表达式
        this.complierText(node)
      } else if (this.isElemenetNode(node)) {
        // 如果是元素节点 我们需要调用元素节点的编译方法，处理元素节点
        this.complierElement(node)
      }
      // 判断node节点是否有子节点，如果有子节点，要递归调用 complier
      if (node.childNodes && node.childNodes.length) {
        this.complier(node)
      }
    })
  }
  // 编译元素节点，处理指令
  complierElement(node) {
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是指令
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        // 对指令做处理
        attrName = attrName.substr(2)
        let key = attr.value
        this.update(node, key, attrName)
      }
    })

  }
  update(node, key, attrName) {
    let updateFn = this[`${attrName}Updater`]
    // 为什么需要这样进行修改呢？ 
    // 1. 首先我们在使用 textUpdater 或 modelUpdater时候 并非 this.xxx 如此调用
    // 2. 如果是 this.xxx 调用的话，那么他的this指向就是 我们的 compiler 
    // 3. 我们回忆一下 函数的调用时的this指向会，是由他的调用式形式所决定的
    // 4. 我们现在这样调用，他的this指向就不是我们所需要的 compiler 对象
    updateFn && updateFn.call(this, node, this.vm[key], key)
  }
  // 处理 v-text 指令
  textUpdater(node, value, key) {
    node.textContent = value
    // 创建 watcher 类, 当数据改变更新视图
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater(node, value, key) {
    node.value = value
    // 创建 watcher 类, 当数据改变更新视图
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // 双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }
  // 处理 v-html 指令
  htmlUpdater(node, value, key) {
    node.innerHTML = value
    new Watcher(this.vm, key, (newValue) => {
      node.innerHTML = newValue
    })
  }
  // 处理 v-on 指令
  onUpdater(node, key, event) {
    node.addEventListener(event, (e) => this.vm[key](e))
  }
  // 编译文本节点，处理差值表达式
  complierText(node) {
    // 正则表达式 解析插值表达式 
    let reg = /\{\{(.+?)\}\}/
    // 文本节点的内容
    let value = node.textContent
    if (reg.test(value)) {
      const key = RegExp.$1.trim()
      node.textContent = value.replace(reg, this.vm[key])

      // 创建 watcher 类, 当数据改变更新视图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // 判断元素属性是否指令
  isDirective(attrName) {
    // 这里判断很简单我们可以会议一下 vue的指令就是 v-xxx开头，所以我们可以这样使用
    return attrName.startsWith('v-')
  }
  /**
   * @description 判断节点是否是文本节点
   * @param {*} node  如果节点是一个元素节点，nodeType 属性返回 1。
   * @param {*} node  如果节点是属性节点, nodeType 属性返回 2。
   * @param {*} node  如果节点是一个文本节点，nodeType 属性返回 3。
   * @param {*} node  如果节点是一个注释节点，nodeType 属性返回 8。
   * @returns Boolean
   */
  isTextNode(node) {
    return node.nodeType === 3
  }
  /**
  * @description 判断节点是否是元素节点
  * @param {*} node  如果节点是一个元素节点，nodeType 属性返回 1。
  * @param {*} node  如果节点是属性节点, nodeType 属性返回 2。
  * @param {*} node  如果节点是一个文本节点，nodeType 属性返回 3。
  * @param {*} node  如果节点是一个注释节点，nodeType 属性返回 8。
  * @returns Boolean
  */
  isElemenetNode(node) {
    // nodeType === 1 就是元素节点
    return node.nodeType === 1
  }
}